<!DOCTYPE html>
<html>
<head>
<title>Polar Clock</title>

<style type="text/css">body{margin:0;}canvas{background:#000;/*HACK: keep scrollbars from showing*/display:block;}</style>

<script type="text/javascript" src="paper.js"></script>

<script type="text/javascript">

var PolarClock = PolarClock || {};



PolarClock.Clock = function() {
	var self = this;
	var _dateTextGroup;

	_rings = [
		new PolarClock.Ring("getSeconds", PolarClock.Options.RingPosition.Seconds, 60, function(index) { return ("0" + index).substr(-2) }, 1),
		new PolarClock.Ring("getMinutes", PolarClock.Options.RingPosition.Minutes, 60, function(index) { return ("0" + index + ":").substr(-3) }, 1),
		new PolarClock.Ring("getHours", PolarClock.Options.RingPosition.Hours, 24, function(index) { return ("0" + index + ":").substr(-3) }, 1),
		new PolarClock.Ring("getMonth", PolarClock.Options.RingPosition.Month, 12, function(index) { return PolarClock.Options.getMonthName(index) + " "; }, 0, positionText),
		new PolarClock.Ring("getDay", PolarClock.Options.RingPosition.DayOfWeek, 7, function(index) { return PolarClock.Options.getDayOfWeekName(index) + " " }, 0, positionText),
		new PolarClock.Ring("getDate", PolarClock.Options.RingPosition.Day,
			function(d) {
                d.setDate(1);
				d.setMonth(d.getMonth() + 1);
				d.setDate(0);
				return d.getDate();
			},
			function(index) {
				var ending;
				switch (index) {
					case 1: case 21: case 31: ending = "st"; break;
					case 2: case 22: ending = "nd"; break;
					case 3: case 23: ending = "rd"; break;
					default : ending = "th";
				}
				return index + ending + " ";
			},
			1, positionText
		)
	];

	self.tick = function() {
		for (var i = 0; i < _rings.length; ++i) {
			_rings[i].tick();
		}
	}

	self.resize = function(event) {
		var minDim = event.size.width < event.size.height ? event.size.width : event.size.height;
		var clockWidth = getClockWidth();
		var clockHeight = clockWidth + 2.5 * PolarClock.Options.RingWidth;  //Handle for text display.
		var zoom = minDim / clockHeight;

		paper.view.center = new Point(0,0);
		paper.view.zoom = zoom;
	}

	function positionText() {
		if (!_dateTextGroup) {
			_dateTextGroup = new Group();
			for (var i = 0; i < _rings.length; ++i) {
				var r = _rings[i];
				_dateTextGroup.addChild(r.getText());
			}
			for (var i = 0; i < _rings.length; ++i) {
				var r = _rings[i];
				_dateTextGroup.insertChild(r.getIndex(), r.getText());
			}
			_dateTextGroup.position.y = -getClockWidth() / 2 - _dateTextGroup.bounds.height / 2;
		}
		for (var i = 1; i < _dateTextGroup.children.length; ++i) {
			var cur = _dateTextGroup.children[i];
			var prev = _dateTextGroup.children[i - 1];
			cur.point.x = prev.point.x + prev.bounds.width;
		}

		_dateTextGroup.bounds.x = -(_dateTextGroup.lastChild.point.x + _dateTextGroup.lastChild.bounds.width - _dateTextGroup.firstChild.point.x) / 2;
	}

	function getClockWidth() {
		var clockWidth =
			2 * PolarClock.Options.InnerRingRadius +  /* The diameter of the innermost circle */
			2 * (_rings.length - 1) * PolarClock.Options.RingSpacing +  /* The spaces between all circles. */
			2 * _rings.length * PolarClock.Options.RingWidth - PolarClock.Options.RingWidth +  /* The width of all circle strokes */
			PolarClock.Options.RingWidth;  /* Extra padding (zero would put outermost circle against edge) */

		return clockWidth;
	}
}



PolarClock.Options = {
	"InnerRingRadius" : 40,
	"RingSpacing" : 10,
	"RingWidth" : 30,
	"GhostRingColor" : "#070707",
	"RingPosition" : { "DayOfWeek" : 0, "Month" : 1, "Day" : 2,	 "Hours" : 3, "Minutes" : 4, "Seconds" : 5 },
	"getMonthName" : function(index) { return ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"][index]; },
	"getDayOfWeekName" : function(index) { return ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][index]; }
}



/*
 * m - String - The function to call on a date object to get the current value for the time unit, e.g. "getHours"
 * i - Integer - The zero based position of this time segment in the radial (0 is innermost).
 * u - Function or Number - How many time segments there are for the unit of measure, e.g. 60 for seconds, 24 for hours
 * textlabel - Function - Function that sets the value for the text, probably not the right place for this.
 * startatone - Integer - The first value to show on the clock. e.g. 0 for month (i.e. January), 1 for date, and 1 for hour.
 * onChange - Function - Method to call when the time value has changed.  e.g. Called when month changes from June to July so text can be updated.
 */
PolarClock.Ring = function (m, i, u, textlabel, startatone, onChange) {
	var self = this;

	var opts = PolarClock.Options;
	var _track = new Path.Circle([0,0], (opts.InnerRingRadius + (opts.RingSpacing + opts.RingWidth) * i));
	var _segmentsShown = 0;
	var _color = new Color();
	var _index = i;
	var _units = (typeof u === "number" ? function() { return u; } : u);
	var _dateMethod = m;
	var _text = new PointText({ font : "consolas,monospace", fontSize : PolarClock.Options.RingWidth });
	var _startatone = startatone;
	var _segments = [];
	var _date = function() { return new Date(); };

	_color.saturation = 1;
	_color.lightness = .5;
	_track.strokeWidth = opts.RingWidth;
	_track.strokeColor = opts.GhostRingColor;
	_text.fillColor = _color;

	var add = function() {
		++_segmentsShown;

		var angle = Math.PI * 2 / _units(_date()) * _segmentsShown;
		var ringRadius = opts.InnerRingRadius + (opts.RingSpacing + opts.RingWidth) * _index;

		if (_segments[_segmentsShown]) {
			_segments[_segmentsShown].visible = true;
		} else if (_units(_date()) === _segmentsShown) {  //A complete ring
			_segments[_segmentsShown] = new Path.Circle([0,0], ringRadius);
			_segments[_segmentsShown].strokeWidth = opts.RingWidth;
			_segments[_segmentsShown].strokeColor = _color;
		} else {
			_segments[_segmentsShown] = new Path.Arc(
				[0, -ringRadius],
				[Math.sin(angle/2) * ringRadius, Math.cos(angle/2) * -ringRadius],
				[Math.sin(angle) * ringRadius, Math.cos(angle) * -ringRadius]
			);

			_segments[_segmentsShown].closed = false;
			_segments[_segmentsShown].strokeWidth = opts.RingWidth;
			_segments[_segmentsShown].strokeColor = _color;
		}

		var previousArc;
		if(previousArc = _segments[_segmentsShown - 1]) { previousArc.visible = false; }

		_color.hue = angle * 180 / Math.PI;
		_text.content = textlabel(_segmentsShown + _startatone - 1);
	}

	var remove = function() {
		var angle = Math.PI * 2 / _units(_date()) * _segmentsShown;

		_segments[_segmentsShown--].visible = false;

		var previousArc;
		if(previousArc = _segments[_segmentsShown - 1]) { previousArc.visible = true; }

		_color.hue = angle * 180 / Math.PI;
		_text.content = textlabel(_segmentsShown);
	}

	self.tick = function() {
		var currentDatePart = _date()[_dateMethod]();

		var segmentsToShow = currentDatePart - _startatone + 1;

		if (_segmentsShown < segmentsToShow) {
			add();
			if (onChange) onChange();
		} else if (_segmentsShown > segmentsToShow) {
			remove();
			if (onChange) onChange();
		}
	}

	self.getText = function() {
		return _text;
	}

	self.getIndex = function() {
		return _index;
	}
}



paper.install(window);

var clock;

window.onload = function() {
	paper.setup('clock');

	clock = new PolarClock.Clock();

	view.onResize = function(event) { clock.resize(event); };
	view.onFrame = function(event) { clock.tick(event); };

	view.onResize(view);
}

</script>
</head>
<body>
	<canvas id="clock" data-paper-resize="true"></canvas>
</body>
</html>